package com.pai4j.pay.service;


import com.pai4j.common.bean.APIResponseBean;
import com.pai4j.common.enums.CommonStatusEnum;
import com.pai4j.common.enums.RedisKeyEnum;
import com.pai4j.common.service.RedisService;
import com.pai4j.common.service.SnowflakeIdGeneratorService;
import com.pai4j.common.util.AkSkGenerator;
import com.pai4j.common.util.IDGenerateUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.SignUtil;
import com.pai4j.domain.dto.payment.PaymentOrderCreateDTO;
import com.pai4j.domain.dto.payment.TransIdDTO;
import com.pai4j.domain.vo.dto.payment.AppAkSkDTO;
import com.pai4j.domain.vo.response.payment.AppBaseResponseVO;
import com.pai4j.pay.config.constants.PaymentConstant;
import com.pai4j.pay.config.exception.PaymentAccessAuthException;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

@Service
public class PaymentApiService {

    @Autowired
    private PaymentAppService appService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private SnowflakeIdGeneratorService snowflakeIdGeneratorService;


    /**
     * 交易凭证获取签名的有效期：5分钟
     */
    private static final Long SIGNATURE_EXPIRE_TIME = 5 * 60 * 1000L;

    /**
     * 生成交易凭证
     *
     * @param appId
     * @param accessKey
     * @param timestamp
     * @param signature
     * @param logId
     * @return
     */
    public String token(Long appId,
                        String accessKey,
                        String secretKey,
                        Long timestamp,
                        String signature,
                        String logId) {


        /**
         * 这里三重校验
         * ------------------------
         * 1. 校验访问凭证
         * 2. 校验校验有效
         * 3. 校验签名
         */
        // 1. 校验访问凭证
        AppBaseResponseVO app = appService.appInfo(appId);
        if (app == null || !CommonStatusEnum.PUBLISHED.getStatusCode().equals(app.getStatus())) {
            throw new PaymentAccessAuthException("应用不存在/未发布");
        }
        AppAkSkDTO akSkDTO = appService.getAppAkSk(appId);
        if (akSkDTO == null) {
            throw new PaymentAccessAuthException("应用不存在");
        }
        boolean checkAk = ObjectUtils.equals(akSkDTO.getAccessKey(), accessKey);
        boolean checkSk = AkSkGenerator.checkKey(secretKey, akSkDTO.getSecretHashKey());
        if (!(checkAk && checkSk)) {
            throw new PaymentAccessAuthException("应用访问凭证校验失败");
        }
        // 2. 校验校验有效
        Long diffTime = System.currentTimeMillis() - timestamp;
        if (diffTime > SIGNATURE_EXPIRE_TIME) {
            throw new PaymentAccessAuthException("签名已过期");
        }
        // 3. 校验签名
        String validSignature = SignUtil.generateSignature(secretKey,
                                                           String.valueOf(appId),
                                                           accessKey,
                                                           String.valueOf(timestamp));
        if (!ObjectUtils.equals(signature, validSignature)) {
            throw new PaymentAccessAuthException("签名校验失败");
        }

        /**
         * 生成并返回接入方access-token（交易凭证，用于后续的交易校验）
         */

        return this.generateAccessToken(appId);
    }

    /**
     * 校验支付交易接口请求token的有效性
     *
     * @param accessToken
     * @return
     */
    public boolean validateAccessToken(String accessToken) {

        /**
         * token 由支付平台生成且未失效 ===> accToken是有效的
         */
        String cacheKey = this.accessTokenCacheKey(accessToken);
        return redisService.exists(cacheKey);
    }

    /**
     * 生成交易事务id
     *
     * @return
     */
    public String transId(String accessToken) {

        Long appId = this.getAppIdFromAccessToken(accessToken);
        Assert.notNull(appId, "交易凭证失效");
        AppBaseResponseVO appBaseResponse = appService.appInfo(appId);
        Assert.notNull(appBaseResponse, "应用不存在，联系客服");
        Assert.isTrue(CommonStatusEnum.PUBLISHED.getStatusCode().equals(appBaseResponse.getStatus()), "应用未发布");

        /**
         * 基于雪花算法生成分布式下唯一id，作为交易分布式事务id，用于后续异常交易快速回滚
         */
        String transId = snowflakeIdGeneratorService.generateId();

        /**
         * 初始化TransId缓存，用于后续事务类操作校验
         */
        this.setPaymentTransIdCache(transId, appId, PaymentConstant.TrasnsIdStatusEnum.INIT);

        return transId;
    }

    /**
     * 走缓存获取分布式事务id详情
     *
     * @param transId
     * @return
     */
    public TransIdDTO getTransIdDTO(String transId) {

        String cacheKey = this.transIdCacheKey(transId);
        String cache = redisService.get(cacheKey);
        if (StringUtils.isBlank(cache)) {
            return null;
        }
        return JsonUtil.fromJson(cache, TransIdDTO.class);
    }

    /**
     * 刷新事务id状态
     *
     * @param transId
     * @param status
     */
    public void setPaymentTransIdCache(String transId, PaymentConstant.TrasnsIdStatusEnum status) {
        TransIdDTO transIdDTO = this.getTransIdDTO(transId);
        Assert.notNull(transIdDTO, "transId is null!");
        this.setPaymentTransIdCache(transId, transIdDTO.getAppId(), status);
    }

    /**
     * 分布式交易事务id添加orderId信息
     *
     * @param transId
     * @param orderId
     */
    public void setPaymentTransOrderInfo(String transId, String orderId) {
        TransIdDTO transIdDTO = this.getTransIdDTO(transId);
        Assert.notNull(transIdDTO, "transId is null!");
        transIdDTO.setOrderId(orderId);
        String orderTranceKey = orderTransIdCacheKey(orderId);
        String cacheKey = this.transIdCacheKey(transId);
        redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime());
        redisService.set(orderTranceKey, transId, RedisKeyEnum.PAYMENT_ORDER_TRANS_ID_CACHE.getExpireTime());
    }

    /**
     * 根据orderId获取分布式事务Id
     * @param orderId
     * @return
     */
    public String getOrderTranceId(String orderId) {
        String orderTranceKey = orderTransIdCacheKey(orderId);
        return redisService.get(orderTranceKey);
    }

    /**
     * 刷新分布式交易事务id缓存
     *
     * @param transId 事务id
     * @param appId   应用id
     * @param status  状态
     */
    private void setPaymentTransIdCache(String transId, Long appId, PaymentConstant.TrasnsIdStatusEnum status) {
        String cacheKey = this.transIdCacheKey(transId);
        TransIdDTO transIdDTO = TransIdDTO.builder()
                                    .transId(transId)
                                    .appId(appId)
                                    .status(status.getStatus())
                                    .build();
        redisService.set(cacheKey, JsonUtil.toJsonString(transIdDTO), RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getExpireTime());
    }


    private String transIdCacheKey(String transId) {

        return RedisKeyEnum.PAYMENT_TRANS_ID_CACHE.getKey(transId);
    }

    private String orderTransIdCacheKey(String orderId) {

        return RedisKeyEnum.PAYMENT_ORDER_TRANS_ID_CACHE.getKey(orderId);
    }

    /**
     * 获取交易凭证背后的应用信息
     *
     * @param accessToken
     * @return
     */
    public Long getAppIdFromAccessToken(String accessToken) {
        String accessTokenCacheKey = this.accessTokenCacheKey(accessToken);
        String cache = redisService.get(accessTokenCacheKey);
        if (StringUtils.isNotBlank(cache)) {
            return Long.valueOf(cache);
        }
        return null;
    }

    private String generateAccessToken(Long appId) {
        /**
         * 生成交易凭证
         */
        String accessToken = IDGenerateUtil.uuid();
        String accessTokenCacheKey = this.accessTokenCacheKey(accessToken);
        redisService.set(accessTokenCacheKey, String.valueOf(appId),
                RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getExpireTime());
        return accessToken;
    }

    private String accessTokenCacheKey(String accessToken) {

        return RedisKeyEnum.PAYMENT_APP_ACCESS_TOKEN_CACHE.getKey(accessToken);
    }
}
